package cn.ac.iie.bdb_to_mongo.bdbhandler;


import cn.ac.iie.bdb_to_mongo.commons.Constants;
import com.sleepycat.je.*;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.UnsupportedEncodingException;

public class BDBUtil<K, V> {

    private final Logger log = LoggerFactory.getLogger(BDBUtil.class);

    private Environment env = null;
    private EnvironmentConfig envCfig = null;
    private Database db = null;
    private DatabaseConfig dbCfig = null;
    private File file = null;
    private String dbName;

    private static BDBUtil instance;

    private BDBUtil() {
    }

    public static BDBUtil getInstance() {
        return instance;
    }

    public static BDBUtil getInstance(Configuration configuration) {
        if (instance == null) {
            synchronized (BDBUtil.class) {
                if (instance == null) {
                    instance = new BDBUtil(configuration.getString(Constants.BDB_ENVHOME), configuration.getString(Constants.BDB_DATABASE_NAME));
                }
            }
        }
        return instance;
    }

    private BDBUtil(String envHome, String dbName) {

        envCfig = new EnvironmentConfig();
        envCfig.setAllowCreate(true);
        envCfig.setTransactional(true);

        file = new File(envHome);
        env = new Environment(file, envCfig);
        dbCfig = new DatabaseConfig();

        dbCfig.setAllowCreate(true);
        dbCfig.setTransactional(true);
        this.dbName = dbName;
        db = env.openDatabase(null, dbName, dbCfig);
    }

    public Environment getEnv() {
        return env;
    }

    public EnvironmentConfig getEnvCfig() {
        return envCfig;
    }

    public Database openDb(String dbName) {
        if (db == null)
            db = env.openDatabase(null, dbName, dbCfig);
        return db;
    }

    public Database getDb() {
        return db;
    }

    public DatabaseConfig getDbCfig() {
        return dbCfig;
    }

    public File getFile() {
        return file;
    }

    public boolean put(K keyStr, V valueStr) {

        DatabaseEntry key;
        DatabaseEntry data;
        try {

            key = new DatabaseEntry(keyStr.toString().getBytes("UTF-8"));
            data = new DatabaseEntry(valueStr.toString().getBytes("UTF-8"));
            OperationStatus status;

            status = db.putNoOverwrite(null, key, data);
            //存在则更新
            if (OperationStatus.KEYEXIST == status)
                return this.update(keyStr, valueStr);
            else
                return OperationStatus.SUCCESS == status;

        } catch (Exception e) {
            e.printStackTrace();
            log.error("bdb put error " + ExceptionUtils.getFullStackTrace(e));
            return false;
        }
    }

    //更新操作（key存在则每次加1）：加事务
    public boolean update(K key, V srcLongVal) {
        boolean succeeded = false;
        Transaction transaction = null;
//        Long value = 1L;//设初始值为增量1
        Long value = Long.valueOf(String.valueOf(srcLongVal));//设初始值为增量

        try {
            DatabaseEntry tmpKey = new DatabaseEntry(key.toString().getBytes("UTF-8"));
            DatabaseEntry tmpData = new DatabaseEntry();

            transaction = env.beginTransaction(null, new TransactionConfig().setReadUncommitted(true));
            if (db.get(null, tmpKey, tmpData, LockMode.READ_UNCOMMITTED) == OperationStatus.SUCCESS) {
                //对bdb内原来的值+1
                value += Long.valueOf(new String(tmpData.getData(), "UTF-8"));
                OperationStatus status = db.put(null, tmpKey, new DatabaseEntry(value.toString().getBytes("UTF-8")));
                if (status == OperationStatus.SUCCESS) {
                    attemptCommit(transaction);
                    return true;
                } else {
                    attemptAbort(transaction);
                    return false;
                }
            } else {
                attemptAbort(transaction);
                return false;
            }

        } catch (UnsupportedEncodingException e) {
            log.error("bdb encode error " + ExceptionUtils.getFullStackTrace(e));
        } catch (DatabaseException e) {
            log.error("bdb get error " + ExceptionUtils.getFullStackTrace(e));
        } catch (Exception e) {
            log.error("bdb update error " + ExceptionUtils.getFullStackTrace(e));
        }
        return false;
    }

    //3.17修改更新操作
    /*//更新操作：加事务
    public boolean update(K key, V srcLongVal) {
        boolean succeeded = false;
        Transaction transaction = null;
        long value;

        try {
            DatabaseEntry tmpKey = new DatabaseEntry(key.toString().getBytes("UTF-8"));
            DatabaseEntry tmpData = new DatabaseEntry();

            transaction = env.beginTransaction(null, new TransactionConfig().setReadUncommitted(true));
            if (db.get(null, tmpKey, tmpData, LockMode.READ_UNCOMMITTED) == OperationStatus.SUCCESS) {
                value = Long.parseLong(new String(tmpData.getData(), "UTF-8"));
                //新值大则更新
                if (Long.parseLong(srcLongVal.toString()) > value) {
                    OperationStatus status = db.put(null, tmpKey, new DatabaseEntry(srcLongVal.toString().getBytes("UTF-8")));
                    if (status == OperationStatus.SUCCESS) {
                        attemptCommit(transaction);
                        return true;
                    } else {
                        attemptAbort(transaction);
                        return false;
                    }
                } else {
                    attemptCommit(transaction);
                    return true;
                }
            } else {
                attemptAbort(transaction);
                return false;
            }

        } catch (UnsupportedEncodingException e) {
            log.error("bdb encode error " + ExceptionUtils.getFullStackTrace(e));
        } catch (DatabaseException e) {
            log.error("bdb get error " + ExceptionUtils.getFullStackTrace(e));
        } catch (Exception e) {
            log.error("bdb update error " + ExceptionUtils.getFullStackTrace(e));
        }
        return false;
    }*/


    /*public String get(K keyStr) {
        DatabaseEntry key;
        String value = null;
        try {
            key = new DatabaseEntry(keyStr.toString().getBytes("UTF-8"));
            DatabaseEntry data = new DatabaseEntry();
            if (db.get(null, key, data, LockMode.READ_UNCOMMITTED) == OperationStatus.SUCCESS)
                value = new String(data.getData(), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("bdb get error " + ExceptionUtils.getFullStackTrace(e));
        }
        return value;
    }*/

    public boolean del(K keyStr) {
        DatabaseEntry key;
        try {
            key = new DatabaseEntry(keyStr.toString().getBytes("UTF-8"));
            if (OperationStatus.SUCCESS == db.delete(null, key))
                return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("bdb del error " + ExceptionUtils.getFullStackTrace(e));
        }
        return false;
    }

    public long bulkDelete() {
        long delNum = 0;
        try {
            db.close();
            db = null;
            env.cleanLog(); //  在关闭环境前清理下日志
            delNum = env.truncateDatabase(null, dbName, true);
            openDb(dbName);
        } catch (Exception e) {
            log.error("bdb bulkDelete error " + ExceptionUtils.getFullStackTrace(e));
        }
        return delNum;
    }

    public long deleteRecords() {

        Cursor myCursor = null;
        CursorConfig config = new CursorConfig();
        config.setReadUncommitted(true);
        myCursor = db.openCursor(null, config);

        DatabaseEntry foundKey = new DatabaseEntry();
        DatabaseEntry foundData = new DatabaseEntry();
        long delNum = 0;

        try {
            while (myCursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
                if (OperationStatus.SUCCESS == db.delete(null, foundKey))
                    ++delNum;
            }
            env.cleanLog();
        } catch (DatabaseException de) {
            log.error("bdb clearLog error " + ExceptionUtils.getFullStackTrace(de));
        } catch (Exception e) {
            log.error("bdb deleteRecords error " + ExceptionUtils.getFullStackTrace(e));
        }
        return delNum;
    }

    public long size() {
        return db.count();
    }

    public void close() {
        try {
            if (db != null) {
                db.close();
                env.cleanLog(); //  在关闭环境前清理下日志
            }
           /* if (env != null) {
                env.cleanLog(); //  在关闭环境前清理下日志
                env.close();
            }*/
        } catch (DatabaseException dbe) {
            log.error("bdb close error" + ExceptionUtils.getFullStackTrace(dbe));
        }
    }

    public Cursor getCursor() {

        CursorConfig cursorConfig = new CursorConfig();
        cursorConfig.setReadUncommitted(true);
        return db.openCursor(null, cursorConfig);

    }

    private void attemptClose(Cursor cursor) {
        try {
            if (cursor != null)
                cursor.close();
        } catch (DatabaseException e) {

        }
    }

    private void attemptCommit(Transaction transaction) {
        try {
            transaction.commit();
        } catch (DatabaseException e) {
            log.error("attemptCommit error" + ExceptionUtils.getFullStackTrace(e));
            attemptAbort(transaction);
        }
    }

    private void attemptAbort(Transaction transaction) {
        try {
            transaction.abort();
        } catch (Exception e) {
            log.error("attemptAbort error" + ExceptionUtils.getFullStackTrace(e));
        }
    }

    public void sync() {
        try {
            env.sync();
        } catch (DatabaseException e) {
            log.error("bdb can not sync" + ExceptionUtils.getFullStackTrace(e));
        }
    }
}